package com.atguigu.gmall.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 全局过滤器：对经过网关所有请求，对所有路由生效。
 *
 * @author: atguigu
 * @create: 2023-08-07 14:44
 */
@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered {


    /**
     * 需要登录才能访问静态页同步请求
     */
    @Value("${authUrls.url}")
    private List<String> authUrls;


    AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * 过滤器业务逻辑
     *
     * @param exchange 封装请求，响应对象
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        log.info("[网关过滤器] 统一鉴权过滤器执行了。");

        //1. 将静态资源请求放行 例如：css样式 js文件 img图片
        //1.1 先获取用户访问地址
        String path = request.getURI().getPath();
        //1.2 通过路径匹配匹配静态资源规则，匹配成功直接放行
        if (antPathMatcher.match("/js/**", path) ||
                antPathMatcher.match("/css/**", path) ||
                antPathMatcher.match("/img/**", path)) {
            return chain.filter(exchange);
        }

        //2. 对于某些Ajax异步请求接口无论登录与否都无法进行访问 例如：/inner/**  响应提示错误信息
        if (antPathMatcher.match("/**/inner/**", path)) {
            return outError(response, ResultCodeEnum.ILLEGAL_REQUEST);
        }

        //3. 在网关中获取到用户提交token令牌，根据令牌查询Redis获取用户ID
        String userId = this.getUserIdFromRedis(request);

        //4. 如果从Redis获取用户ID为空-用户未登录  用户访问同步静态页需要认证才能访问；用户访问Ajax异步请求需要认证才能访问
        if (StringUtils.isBlank(userId)) {
            //4.1 情况一：访问需要登录才能访问静态页同步请求，跳转到登录页面
            if (!CollectionUtils.isEmpty(authUrls)) {
                for (String authUrl : authUrls) {
                    if (antPathMatcher.match("/" + authUrl, path)) {
                        //跳转到登录页面 //http状态码设置303
                        response.setStatusCode(HttpStatus.SEE_OTHER);
                        //重定向页面  浏览器会重定向到指定页面
                        response.getHeaders().add(HttpHeaders.LOCATION, "http://passport.gmall.com/login.html?originUrl=" + request.getURI());
                        //停止路由转发
                        return response.setComplete();
                    }
                }
            }
            //4.2 情况二：访问Ajax异步请求 地址中包含/auth/登录才能访问，响应错误信息，未登录不允许访问
            if (antPathMatcher.match("/**/auth/**", path)) {
                return outError(response, ResultCodeEnum.LOGIN_AUTH);
            }
        } else {
            //5. 说明用户登录，路由转发请求前，将当前用户ID透传到下游目标微服务中
            request.mutate().header("userId", userId);
        }

        //6. 将购物车模块需要临时用户ID获取到，将临时用户ID传递到下游购物车服务中
        String userTempId = getUserTempId(request);
        if (StringUtils.isNotBlank(userTempId)) {
            request.mutate().header("userTempId", userTempId);
        }
        return chain.filter(exchange);
    }


    /**
     * 获取临时用户ID
     *
     * @param request
     * @return
     */
    private String getUserTempId(ServerHttpRequest request) {
        String userTempId = "";
        //1.先获取token 优先从Cookie中获取token
        HttpCookie tokenCookie = request.getCookies().getFirst("userTempId");
        if (tokenCookie != null) {
            userTempId = tokenCookie.getValue();
            return userTempId;
        } else {
            //2.如果Cookie中未提交，再从请求头中获取
            userTempId = request.getHeaders().getFirst("userTempId");
            return userTempId;
        }
    }

    /**
     * 通过客户端提交Token获取用户ID，客户端提交Token途径两种：
     * * 通过请求头提交token
     * * 通过Cookie提交token
     *
     * @param request
     * @return
     */
    private String getUserIdFromRedis(ServerHttpRequest request) {
        String token = "";
        //1.先获取token 优先从Cookie中获取token
        HttpCookie tokenCookie = request.getCookies().getFirst("token");
        if (tokenCookie != null) {
            token = tokenCookie.getValue();
        } else {
            //2.如果Cookie中未提交，再从请求头中获取
            token = request.getHeaders().getFirst("token");
        }
        //3.拼接用户登录key 查询Redis获取用户ID
        if (StringUtils.isNotBlank(token)) {
            String key = "user:" + token;
            //判断key是否存在，如果存在获取key剩余过期时间，判断剩余ttl时间是否小于规定阈值（2小时）自动续期为24小时
            Boolean flag = redisTemplate.hasKey(key);
            if (flag) {
                //说明用户在登录状态，获取key的剩余时间 单位：秒
                Long expire = redisTemplate.getExpire(key);
                if (expire <= 7200) {
                    //满足续期要求，自动续期为24小时
                    redisTemplate.expire(key, 24, TimeUnit.HOURS);
                }
                JSONObject jsonObj = (JSONObject) redisTemplate.opsForValue().get(key);
                if (jsonObj != null) {
                    return jsonObj.getString("id");
                }
            }
        }
        return null;
    }


    /**
     * 过滤器执行顺序，值越小优先级越高
     *
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }


    /**
     * 用来给前端响应错误提示信息
     *
     * @param response
     * @param resultCodeEnum
     * @return
     */
    private Mono<Void> outError(ServerHttpResponse response, ResultCodeEnum resultCodeEnum) {
        //1.准备响应结果对象,转为JSON对象
        Result<Object> result = Result.build(null, resultCodeEnum);
        String resultString = JSON.toJSONString(result);

        //2.响应结果给客户端
        //2.1 设置http状态码
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        //2.2 通过响应头设置响应数据格式-json
        response.getHeaders().add("content-type", "application/json;charset=utf-8");
        DataBuffer wrap = response.bufferFactory().wrap(resultString.getBytes());
        //2.4 网关将响应数据返回给客户端
        return response.writeWith(Mono.just(wrap));
    }


    public static void main(String[] args) {
        //String path = "/js/plugins/jquery/jquery.min.js";
        //String path = "/api/product/inner/getCategoryView/61";
        String path = "/trade.html";
        //路径匹配器
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        boolean match = antPathMatcher.match("/trade.html", path);
        System.out.println(match);
    }
}
